﻿///////////////////////////////////////////////////////////////////////////////
//
// This file is part of the BassDrop project
//   https://code.google.com/p/bass-drop/
//
//
// Creator:	Gergely Krajcsovszki (krajcsovszkig@hotmail.com)
// Date:	2014-03-01 14:00
//
//
// Copyright © 2013-2014. Gergely Krajcsovszki
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BassDrop.Core.Common.Cloud.Assets;
using BassDrop.Core.Common.Data.IO.Portable;
using BassDrop.Core.Common.Services;
using BassDrop.Core.Common.Utilities;

namespace BassDrop.Core.Common.Cloud.Requests
{
	/// <summary>
	/// Contains factory methods for cloud requests
	/// </summary>
	public static class RequestFactory
	{
		/// <summary>
		/// The extension of lock files starting with the .
		/// </summary>
		private static readonly string LockfileExtension = ".BassDrop.lock";

		/// <summary>
		/// The default lock timeout in milliseconds (1 min)
		/// </summary>
		public const long LockTimeout = 60000; // TODO: dinamikus lockolási idő

		/// <summary>
		/// The time needed to create a lock in milliseconds, added to the requested timeout
		/// </summary>
		private const long LockOverhead = 1000;

		/// <summary>
		/// The time between two checks in milliseconds - may be modified by a random factor, that doubles it at most
		/// </summary>
		private const long RepeatTimeout = 500;

		/// <summary>
		/// Random generator used for random wait times
		/// </summary>
		private static readonly Random s_random = new Random();

		/// <summary>
		/// The type of callback functions for passing request results
		/// </summary>
		/// <typeparam name="T">the type of the result</typeparam>
		/// <param name="result">the result of the request</param>
		/// <param name="connection">the connection that was used to obtain the result</param>
		public delegate Task ResultCallbackFunction<T>( T result, CloudConnectionBase connection );

		private static ulong s_lockId = 0;

		/// <summary>
		/// Returns a new lock ID
		/// </summary>
		/// <returns></returns>
		public static ulong GenerateLockId()
		{
			return ++s_lockId;
		}

		/// <summary>
		/// Creates a compound request from the passed individual requests. They will be executed in an atomic manner in the
		/// same order they are specified.
		/// </summary>
		/// <param name="mergeable">Is the request allowed to be merged with others</param>
		/// <param name="requests">The requests to include in the compound request</param>
		/// <returns></returns>
		public static CloudRequestBase MakeCompoundRequest( bool mergeable, params CloudRequestBase[] requests )
		{
			var request = new CloudRequestCompound( mergeable );

			foreach ( var item in requests )
			{
				request.Add( item );
			}

			return request;
		}

		/// <summary>
		/// Creates a compound request from the passed individual requests. They will be executed in an atomic manner in the
		/// same order they are specified. The resulting request will be mergeable.
		/// </summary>
		/// <param name="requests">The requests to include in the compound request</param>
		/// <returns></returns>
		public static CloudRequestBase MakeCompoundRequest( params CloudRequestBase[] requests )
		{
			return MakeCompoundRequest( true, requests );
		}

		/// <summary>
		/// Constructs a new callback request
		/// </summary>
		/// <param name="callbackFunction">The function to be called when the request is executed</param>
		/// <param name="mergeable">Is this request allowed to be merged with others</param>
		public static CloudRequestBase MakeCallbackRequest( CloudRequestCallback.CallbackFunction callbackFunction, bool mergeable = true )
		{
			return new CloudRequestCallback( callbackFunction, mergeable );
		}

		/// <summary>
		/// Copies a file from the cloud to the local filesystem.
		/// Overwrites any existing files.
		/// </summary>
		/// <param name="remotePath">path to the file in the cloud</param>
		/// <param name="localPath">path to the file in the local filesystem</param>
		/// <param name="mergeable">Is the request allowed to be merged with others</param>
		/// <returns>A new compound request that performs a get operation</returns>
		public static CloudRequestBase MakeGetRequest( string remotePath, Uri localPath, bool mergeable = true )
		{
			var lockId = GenerateLockId();

			return MakeCompoundRequest( mergeable,
				MakeLockRequest( remotePath, lockId, LockTimeout, false, mergeable: mergeable ),
				new CloudRequestGet( remotePath, localPath, mergeable ), // TODO: ha közben lejár a lock akkor mi legyen?
				MakeUnlockRequest( remotePath, lockId, mergeable: mergeable ) );
		}

		/// <summary>
		/// Copies a file from the local filesystem to the cloud.
		/// Overwrites any existing files.
		/// </summary>
		/// <param name="remotePath">path to the file in the cloud</param>
		/// <param name="localPath">path to the file in the local filesystem</param>
		/// <param name="mergeable">Is the request allowed to be merged with others</param>
		/// <returns>A new compound request that performs a put operation</returns>
		public static CloudRequestBase MakePutRequest( string remotePath, Uri localPath, bool mergeable = true )
		{
			var lockId = GenerateLockId();

			return MakeCompoundRequest( mergeable,
				MakeLockRequest( remotePath, lockId, LockTimeout, true, mergeable: mergeable ),
				new CloudRequestPut( remotePath, localPath, mergeable ), // TODO: ha közben lejár a lock akkor mi legyen?
				MakeUnlockRequest( remotePath, lockId, mergeable: mergeable ) );
		}

		/// <summary>
		/// Checks if a file exists in the cloud.
		/// </summary>
		/// <param name="remotePath">path to the file in the cloud</param>
		/// <param name="result">function called when the request is completed and gets the result of it</param>
		/// <param name="mergeable">Is the request allowed to be merged with others</param>
		/// <returns>A new compound request that performs an exists operation</returns>
		public static CloudRequestBase MakeExistsRequest( string remotePath, ResultCallbackFunction<bool> result, bool mergeable = true )
		{
			var existsRequest = new CloudRequestExists( remotePath, mergeable );

			return MakeCompoundRequest( mergeable,
				existsRequest,
				MakeCallbackRequest( async ( req, conn ) =>
					{
						await result( (bool) existsRequest.Result, conn );
					},
					mergeable ) );
		}

		/// <summary>
		/// Gets the metadata of a file in the cloud.
		/// </summary>
		/// <param name="remotePath">path to the file in the cloud</param>
		/// <param name="result">function called when the request is completed and gets the result of it</param>
		/// <param name="mergeable">Is the request allowed to be merged with others</param>
		/// <returns>A new compound request that performs an exists operation</returns>
		public static CloudRequestBase MakeGetMetadataRequest( string remotePath, ResultCallbackFunction<FileMetadata> result, bool mergeable = true )
		{
			var getMetadataRequest = new CloudRequestGetMetadata( remotePath, mergeable );

			return MakeCompoundRequest( mergeable,
				getMetadataRequest,
				MakeCallbackRequest( async ( req, conn ) =>
					{
						await result( getMetadataRequest.Result, conn );
					},
					mergeable ) );
		}

		/// <summary>
		/// Returns a list of file paths matching a regular expression from baseDir.
		/// </summary>
		/// <param name="baseDir">path to the base directory of the search</param>
		/// <param name="regex">regular expression to match</param>
		/// <param name="recursive">true to search subfolders</param>
		/// <param name="result">function called when the request is completed and gets the result of it</param>
		/// <param name="mergeable">Is the request allowed to be merged with others</param>
		/// <returns>A new compound request that performs a find operation</returns>
		public static CloudRequestBase MakeFindRequest( string baseDir, Regex regex, bool recursive, ResultCallbackFunction<List<string>> result, bool mergeable = true )
		{
			var findRequest = new CloudRequestFind( baseDir, regex, recursive, mergeable );

			return MakeCompoundRequest( mergeable,
				findRequest,
				MakeCallbackRequest( async ( req, conn ) =>
					{
						await result( findRequest.Result, conn );
					},
					mergeable ) );
		}

		/// <summary>
		/// Moves/renames a file inside the cloud. Overwriting existing files is not allowed.
		/// </summary>
		/// <param name="oldPath">the old path to the file in the cloud</param>
		/// <param name="newPath">the new (desired) path to the file in the cloud</param>
		/// <param name="mergeable">Is the request allowed to be merged with others</param>
		/// <returns>A new compound request that performs a move operation</returns>
		public static CloudRequestBase MakeMoveRequest( string oldPath, string newPath, bool mergeable = true )
		{
			var lockId = GenerateLockId();

			return MakeCompoundRequest( mergeable,
				MakeLockRequest( oldPath, lockId, LockTimeout, true, mergeable: mergeable ), // TODO: mennyi időre lockoljon?
				MakeLockRequest( newPath, lockId, LockTimeout, true, mergeable: mergeable ),
				new CloudRequestMove( oldPath, newPath, mergeable ), // TODO: ha közben lejár a lock akkor mi legyen?
				MakeUnlockRequest( oldPath, lockId, mergeable: mergeable ),
				MakeUnlockRequest( newPath, lockId, mergeable: mergeable ) );
		}

		/// <summary>
		/// Deletes a file from the cloud if it exists.
		/// </summary>
		/// <param name="remotePath">path to the file in the cloud</param>
		/// <param name="mergeable">Is the request allowed to be merged with others</param>
		/// <returns>A new compound request that performs a delete operation</returns>
		public static CloudRequestBase MakeDeleteRequest( string remotePath, bool mergeable = true )
		{
			var lockId = GenerateLockId();

			return MakeCompoundRequest( mergeable,
				MakeLockRequest( remotePath, lockId, LockTimeout, true, mergeable: mergeable ),
				new CloudRequestDelete( remotePath, mergeable ), // TODO: ha közben lejár a lock akkor mi legyen?
				MakeUnlockRequest( remotePath, lockId, mergeable: mergeable ) );
		}

		// TODO: mark deleted requestek

		/// <summary>
		/// Locks a file in the cloud. Blocks until the lock is acquired.
		/// The lock will be released after <code>timeoutMillis</code>
		/// </summary>
		/// <param name="remotePath">path to the file in the cloud</param>
		/// <param name="lockId">the ID of the lock.</param>
		/// <param name="timeoutMillis">lock timeout in milliseconds</param>
		/// <param name="exclusive">create an exclusive lock. If false, reading the file is still permitted.</param>
		/// <param name="mergeable">Is the request allowed to be merged with others</param>
		/// <returns>A new compound request that performs a lock operation</returns>
		public static CloudRequestBase MakeLockRequest( string remotePath, ulong lockId, long timeoutMillis, bool exclusive, bool mergeable = true )
		{
			return MakeCallbackRequest( async ( req, conn ) => // TODO: virtuális függvény legyen a ConnectionBase-ben, saját request osztállyal
				{
					string lockPath = LockPathFor( remotePath, conn );
					bool first = true;

					do
					{
						if ( !first )
						{
							// Wait some before trying again
							ThreadingUtils.Wait( (int) RepeatTimeout + s_random.Next( (int) RepeatTimeout ) );
						}
						else
						{
							first = false;
						}

						LockInfo? myLock = await GetMyLock( remotePath, conn );

						if ( myLock == null || (exclusive && ((LockInfo) myLock).Type == LockInfo.LockType.Shared) )
						{
							myLock = new LockInfo(
								conn.ClientID,
								myLock != null ? new HashSet<ulong>( ((LockInfo) myLock).LockIds.Union( new[] { lockId } ) ) : new HashSet<ulong>( new[] { lockId } ),
								exclusive ? LockInfo.LockType.Exclusive : LockInfo.LockType.Shared,
								conn.Clocks.GetCopy(),
								DateTime.UtcNow + TimeUtils.MillisToTimeSpan( timeoutMillis + LockOverhead ) );
						}
						else
						{
							((LockInfo) myLock).EnsureValidUntil( DateTime.UtcNow + TimeUtils.MillisToTimeSpan( timeoutMillis + LockOverhead ) );
						}

						LockInfo info = (LockInfo) myLock;
						await WriteLock( info, lockPath, conn );
					} while ( (await GetLock( remotePath, conn, true ) ?? new LockInfo()).Owner != conn.ClientID );
				},
				mergeable );
		}

		/// <summary>
		/// Unlocks a file in the cloud if it was locked by this client.
		/// Can place locks for nonexistent files as well.
		/// </summary>
		/// <param name="remotePath">path to the file in the cloud</param>
		/// <param name="lockId">the ID of the lock to delete</param>
		/// <param name="mergeable">Is the request allowed to be merged with others</param>
		/// <returns>A new compound request that performs an unlock operation</returns>
		public static CloudRequestBase MakeUnlockRequest( string remotePath, ulong lockId, bool mergeable = true )
		{
			return MakeCallbackRequest( async ( req, conn ) =>
					{
						string lockPath = LockPathFor( remotePath, conn );
						LockInfo? lockInfo = await ReadLock( lockPath, conn ); // this deletes it if it has expired

						if ( lockInfo != null )
						{
							LockInfo info = (LockInfo) lockInfo;
							info.LockIds.Remove( lockId );

							if ( info.LockIds.Count == 0 )
							{
								await conn.Delete( lockPath );
							}
							else
							{
								await WriteLock( info, lockPath, conn );
							}
						}
					},
					mergeable );
		}

		/// <summary>
		/// Checks if a file is locked and returns the lock information or null if it isn't
		/// </summary>
		/// <param name="remotePath">path to the file in the cloud</param>
		/// <param name="result">function called when the request is completed and gets the result of it</param>
		/// <param name="mergeable">Is the request allowed to be merged with others</param>
		/// <returns>A new compound request that performs an isLocked operation</returns>
		public static CloudRequestBase MakeGetLockRequest( string remotePath, ResultCallbackFunction<LockInfo?> result, bool mergeable = true )
		{
			return MakeCallbackRequest( async ( req, conn ) =>
				{
					await result( await GetLock( remotePath, conn ), conn );
				},
				mergeable );
		}

		/// <summary>
		/// Checks if a file is locked
		/// </summary>
		/// <param name="remotePath">path to the file in the cloud</param>
		/// <param name="result">function called when the request is completed and gets the result of it</param>
		/// <param name="mergeable">Is the request allowed to be merged with others</param>
		/// <returns>A new compound request that performs an isLocked</returns>
		public static CloudRequestBase MakeIsLockedRequest( string remotePath, ResultCallbackFunction<bool> result, bool mergeable = true )
		{
			return MakeGetLockRequest( remotePath, async ( info, conn ) =>
				{
					await result( info != null, conn );
				},
				mergeable );
		}

		/// <summary>
		/// Opens a cloud file, passes the stream to the result callback which can read and write it, then
		/// sends back the modified file. Similar to System.IO.File.Open.
		/// </summary>
		/// <param name="remotePath">path to the file in the cloud</param>
		/// <param name="mode">A FileMode value that specifies whether a file is created if one does not exist, and determines whether the contents of existing files are retained or overwritten.</param>
		/// <param name="access">A FileAccess value that specifies the operations that can be performed on the file.</param>
		/// <param name="share">A FileShare value specifying the type of access other threads have to the file.</param>
		/// <param name="result">function called when the file is opened and gets the stream for it</param>
		/// <param name="mergeable">Is the request allowed to be merged with others</param>
		/// <returns>A new compound request that performs an open operation</returns>
		public static CloudRequestBase MakeOpenRequest(
			string remotePath,
			PortableFileMode mode,
			PortableFileAccess access,
			PortableFileShare share,
			ResultCallbackFunction<Stream> result,
			bool mergeable = true )
		{
			var lockId = GenerateLockId();

			var request = new CloudRequestCompound( mergeable );
			string tempPath = ServiceCenter.FileHandler.GetTempFileName();

			bool exclusive =
				(access == PortableFileAccess.Write
					|| access == PortableFileAccess.ReadWrite)
				&& (share == PortableFileShare.None
					|| share == PortableFileShare.Delete
					|| share == PortableFileShare.Inheritable
					|| share == PortableFileShare.Write);

			bool needGet = access == PortableFileAccess.Read || access == PortableFileAccess.ReadWrite;
			bool needPut = access == PortableFileAccess.Write || access == PortableFileAccess.ReadWrite;

			request.Add( MakeLockRequest( remotePath, lockId, 2 * LockTimeout, exclusive, mergeable: mergeable ) ); // TODO: idő!

			if ( needGet )
			{
				request.Add( new CloudRequestGet( remotePath, new Uri( tempPath ), mergeable ) );
			}

			request.Add( MakeCallbackRequest( async ( req, conn ) =>
				{
					using ( var stream = ServiceCenter.FileHandler.GetStream( tempPath, mode, access, share, true ) )
					{
						await result( stream, conn );
					}
				},
				mergeable ) );

			if ( needPut )
			{
				request.Add( new CloudRequestPut( remotePath, new Uri( tempPath ), mergeable ) );
			}

			// TODO: ha közben lejár a lock akkor mi legyen?

			request.Add( MakeUnlockRequest( remotePath, lockId, mergeable: mergeable ) );

			request.Add( MakeCallbackRequest( ( req, conn ) =>
					{
						ServiceCenter.FileHandler.Delete( tempPath );
						return ThreadingUtils.CompletedTask();
					},
					mergeable ),
				true );

			return request;
		}

		/// <summary>
		/// Returns the text content of a remote file
		/// </summary>
		/// <param name="remotePath">path to the file in the cloud</param>
		/// <param name="result">function called when the request is completed and gets the result of it</param>
		/// <param name="mergeable">Is the request allowed to be merged with others</param>
		/// <returns>A new compound request that performs a readAllText operation</returns>
		public static CloudRequestBase MakeReadAllTextRequest( string remotePath, ResultCallbackFunction<string> result, bool mergeable = true )
		{
			var request = new CloudRequestCompound( mergeable ); // TODO: GetText requestet használjon
			string tempPath = ServiceCenter.FileHandler.GetTempFileName();

			request.Add( new CloudRequestGet( remotePath, new Uri( tempPath ), mergeable ) );
			request.Add( MakeCallbackRequest( async ( req, conn ) =>
				{
					await result( ServiceCenter.FileHandler.ReadAllText( tempPath ), conn );
				},
				mergeable ) );
			request.Add( MakeCallbackRequest( ( req, conn ) =>
					{
						ServiceCenter.FileHandler.Delete( tempPath );
						return ThreadingUtils.CompletedTask();
					},
					mergeable ),
				true );

			return request;
		}

		/// <summary>
		/// Writes some text into a remote file overwriting its contents
		/// </summary>
		/// <param name="remotePath">path to the file in the cloud</param>
		/// <param name="text">the text to write into the file</param>
		/// <param name="mergeable">Is the request allowed to be merged with others</param>
		/// <returns>A new compound request that performs a writeAllText operation</returns>
		public static CloudRequestBase MakeWriteAllTextRequest( string remotePath, string text, bool mergeable = true )
		{
			var request = new CloudRequestCompound( mergeable );
			string tempPath = ServiceCenter.FileHandler.GetTempFileName();

			ServiceCenter.FileHandler.WriteAllText( tempPath, text );

			request.Add( new CloudRequestPut( remotePath, new Uri( tempPath ), mergeable ) );
			request.Add( MakeCallbackRequest( ( req, conn ) =>
					{
						ServiceCenter.FileHandler.Delete( tempPath );
						return ThreadingUtils.CompletedTask();
					},
					mergeable ),
				true );

			return request;
		}

		#region Helper methods

		/// <summary>
		/// Returns the path of the lock file for the remote file on the passed path
		/// </summary>
		/// <param name="remotePath"></param>
		/// <param name="connection"></param>
		/// <returns></returns>
		private static string LockPathFor( string remotePath, CloudConnectionBase connection )
		{
			return remotePath + "." + connection.ClientID + LockfileExtension;
		}

		/// <summary>
		/// Finds the currently valid lock file (the oldest non-expired one) and returns the lock information from it.
		/// If no valid lock is found, null is returned. Also deletes expired lock files.
		/// </summary>
		/// <param name="remotePath">the file to get the lock for</param>
		/// <param name="connection">the connection to the file</param>
		/// <param name="preferMine">if true, this clients lock is returned when it is shared with other locks. (Otherwise the earliest one is returned)</param>
		/// <returns></returns>
		private static async Task<LockInfo?> GetLock( string remotePath, CloudConnectionBase connection, bool preferMine = false )
		{
			LockInfo? lockInfo = null;
			List<LockInfo> validLocks = new List<LockInfo>();

			// Find locks
			List<string> lockFiles = await connection.Find(
					ServiceCenter.FileHandler.GetDirectoryName( remotePath ),
					new Regex( Regex.Escape( ServiceCenter.FileHandler.GetFileName( remotePath ) + "." ) + ".*" + Regex.Escape( LockfileExtension ) ),
					false );

			foreach ( var lockFile in lockFiles )
			{
				LockInfo? info = await ReadLock( lockFile, connection );

				if ( info != null )
				{
					if ( lockInfo == null || ((LockInfo) info).Issued < ((LockInfo) lockInfo).Issued )
					{
						lockInfo = info;
					}

					validLocks.Add( (LockInfo) info );
				}
			}

			// If a lock with the same timestamp as mine exists, invalidate my lock
			LockInfo? myLock =
						  (from item
						   in validLocks
						   where item.Owner == connection.ClientID
						   select item).FirstOrDefault();

			if ( myLock != null )
			{
				var sameTimestamps =
					from item
					in validLocks
					where item.Issued == ((LockInfo) myLock).Issued && item.Owner != connection.ClientID
					select item;

				if ( sameTimestamps.Count() > 0 )
				{
					validLocks.Remove( (LockInfo) myLock );
					await connection.Delete( ((LockInfo) myLock).Path );

					if ( lockInfo != null && ((LockInfo) lockInfo).Owner == connection.ClientID )
					{
						lockInfo = validLocks.Aggregate( ( minimum, item ) => minimum.Issued < item.Issued ? minimum : item ); // replace the lockinfo with the oldest valid lock
					}
				}
			}

			// Find shared locks
			if ( lockInfo != null && ((LockInfo) lockInfo).Type == LockInfo.LockType.Shared )
			{
				validLocks.Sort( ( x, y ) =>
				{
					return x.Issued.CompareTo( y.Issued );
				} );

				List<LockInfo> sharedLocks = new List<LockInfo>();

				foreach ( var item in validLocks )
				{
					if ( item.Type == LockInfo.LockType.Shared )
					{
						sharedLocks.Add( item );
					}
					else
					{
						break;
					}
				}

				if ( preferMine )
				{
					LockInfo? mySharedLock =
						  (from item
						   in sharedLocks
						   where item.Owner == connection.ClientID
						   select item).FirstOrDefault();

					if ( mySharedLock != null )
					{
						lockInfo = mySharedLock;
					}
				}

				sharedLocks.Remove( (LockInfo) lockInfo );

				LockInfo ret = (LockInfo) lockInfo;
				ret.SharedWith = sharedLocks;
				return ret;
			}

			return lockInfo;
		}

		/// <summary>
		/// Returns the lock file of this client for the passed remote file. If no valid lock is found,
		/// null is returned. If an invalid lock is found, it is deleted.
		/// </summary>
		/// <param name="remotePath"></param>
		/// <param name="connection"></param>
		/// <returns></returns>
		private static async Task<LockInfo?> GetMyLock( string remotePath, CloudConnectionBase connection )
		{
			string lockPath = LockPathFor( remotePath, connection );

			if ( await connection.Exists( lockPath ) )
			{
				return await ReadLock( lockPath, connection );
			}

			return null;
		}

		/// <summary>
		/// Gets and parses the lock file on the given path. If it is expired deletes it and returns null. If no lockfile is found on the path, also returns null.
		/// </summary>
		/// <param name="lockPath"></param>
		/// <param name="connection"></param>
		/// <returns></returns>
		private static async Task<LockInfo?> ReadLock( string lockPath, CloudConnectionBase connection )
		{
			string tempPath = ServiceCenter.FileHandler.GetTempFileName();
			bool corrupted = false;

			try
			{
				await connection.Get( lockPath, new Uri( tempPath ) );
				LockInfo info = LockInfo.Parse( ServiceCenter.FileHandler.ReadAllText( tempPath ) );
				info.Path = lockPath;

				if ( info.GetValidFor( connection.Clocks.Find( info.Owner ).CurrentTimeEstimate ) == 0 )
				{
					await connection.Delete( lockPath ); // delete expired lock file
				}
				else
				{
					return info;
				}
			}
			catch ( FileNotFoundException )
			{
				// ignore, lock missing
			}
			catch ( Exception )
			{
				corrupted = true;
			}
			finally
			{
				ServiceCenter.FileHandler.Delete( tempPath );
			}

			if ( corrupted )
			{
				await connection.Delete( lockPath ); // delete corrupted lock file
				System.Diagnostics.Debug.Assert( false, "Deleted corrupted lock file at " + lockPath );
			}

			return null;
		}

		/// <summary>
		/// Writes the passed lock info to the passed lock path on the passed connection
		/// </summary>
		/// <param name="lockInfo"></param>
		/// <param name="lockPath"></param>
		/// <param name="connection"></param>
		private static async Task WriteLock( LockInfo lockInfo, string lockPath, CloudConnectionBase connection )
		{
			string tempPath = ServiceCenter.FileHandler.GetTempFileName();

			try
			{
				// Create the lock file locally
				ServiceCenter.FileHandler.WriteAllText(
					tempPath,
					lockInfo.ToLockFileText() );

				await connection.Put( lockPath, new Uri( tempPath ) );
			}
			finally
			{
				ServiceCenter.FileHandler.Delete( tempPath );
			}
		}

		#endregion Helper methods
	}
}

